package org.ncibi.cytoscape.metscape.task;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.ncibi.cytoscape.metscape.data.Attributes;
import org.ncibi.cytoscape.metscape.data.CompoundData;
import org.ncibi.cytoscape.metscape.data.Concept;
import org.ncibi.cytoscape.metscape.data.ConceptData;
import org.ncibi.cytoscape.metscape.data.GeneData;
import org.ncibi.cytoscape.metscape.data.Organism;
import org.ncibi.cytoscape.metscape.network.MetScapeNetworks;
import org.ncibi.cytoscape.metscape.plugin.MetScapePlugin;
import org.ncibi.cytoscape.metscape.plugin.PluginData;
import org.ncibi.cytoscape.metscape.ui.MissingDataDialog;
import org.ncibi.metab.network.MetabolicNetwork;
import org.ncibi.metab.network.NetworkType;
import org.ncibi.metab.network.node.CompoundNode;
import org.ncibi.metab.network.node.GeneNode;
import org.ncibi.metab.network.node.MetabolicNode;
import org.ncibi.metab.network.node.NodeType;
import org.ncibi.metab.ws.client.MetabolicNetworkService;
import org.ncibi.metab.ws.client.MetabolicNodesService;
import org.ncibi.ws.HttpRequestType;
import org.ncibi.ws.Response;

import cytoscape.CyNode;
import cytoscape.Cytoscape;
import cytoscape.task.Task;
import cytoscape.task.TaskMonitor;
import cytoscape.task.ui.JTaskConfig;
import cytoscape.task.util.TaskManager;

public class GetDataMappingsTask implements Task {

	private TaskMonitor taskMonitor;
	private boolean interrupted = false;
	private CompoundData compoundData;
	private GeneData geneData;
	private ConceptData conceptData;
	private Organism organism;

	public static boolean getMappings(CompoundData compoundData, GeneData geneData,
			ConceptData conceptData, Organism organism) {
		GetDataMappingsTask task = new GetDataMappingsTask(compoundData, geneData, 
				conceptData, organism);
		return configureAndRunTask(task);
	}
	
	private GetDataMappingsTask(CompoundData compoundData, GeneData geneData, 
			ConceptData conceptData, Organism organism) {
		this.compoundData = compoundData;
		this.geneData = geneData;
		this.conceptData = conceptData;
		this.organism = organism;
	}

	private static boolean configureAndRunTask(GetDataMappingsTask task) {
		// Configure JTask Dialog Pop-Up Box
		JTaskConfig jTaskConfig = new JTaskConfig();
		jTaskConfig.setOwner(Cytoscape.getDesktop());
		jTaskConfig.displayCancelButton(true);
		jTaskConfig.displayStatus(true);

		// Execute Task in New Thread; pops open JTask Dialog Box.
		if(TaskManager.executeTask(task, jTaskConfig))
			return true;
		else return false;
	}

	public void setTaskMonitor(TaskMonitor monitor)
			throws IllegalThreadStateException {
		taskMonitor = monitor; 
	}

	public void halt() {
		interrupted = true;
	}

	public String getTitle() {
		return "Get Data from Server";
	}

	public void run() {
		try {
			interrupted = false;
			taskMonitor.setPercentCompleted(-1);
			taskMonitor.setStatus("Initializing...");
			taskMonitor.setStatus("Getting mappings for genes and compounds...");
			
			Set<String> inputCompounds = new HashSet<String>();
			Set<Integer> inputGenes = new HashSet<Integer>();
			Map<Integer,String> geneIdForDataId = new HashMap<Integer, String>();
			
			Map<String, String> mappedCompounds = new TreeMap<String,String>();
			Map<Integer, String> mappedGenes = new TreeMap<Integer, String>();
	
			Set<String> missingCompounds = new TreeSet<String>();
			Set<Integer> missingGenes = new TreeSet<Integer>();
			Set<Concept> missingConcepts = new TreeSet<Concept>();
			
			inputCompounds.addAll(compoundData.idSet());
			inputGenes.addAll(geneData.idSet());
			if(!conceptData.isEmpty()) {
				if(!geneData.isEmpty()) {
					inputGenes.retainAll(conceptData.getGeneSet());
				}
				else {
					inputGenes.addAll(conceptData.getGeneSet());
				}
			}
			
			MetabolicNodesService nodesService = new MetabolicNodesService(
					HttpRequestType.POST);
			Response<Set<MetabolicNode>> nodesResponse = nodesService
					.retrieveCompoundsAndGenesWithInteractions(inputCompounds,
							inputGenes, organism.getTaxid());
			
			if (nodesResponse == null || !nodesResponse.getResponseStatus().isSuccess() || interrupted)
				throw new Exception("Failed to retrieve gene and compound mappings.");

			Set<MetabolicNode> nodesSet = nodesResponse.getResponseValue();
			for(MetabolicNode node: nodesSet) {
				if(node.isType(NodeType.COMPOUND)) {
					mappedCompounds.put(CompoundNode.getCid(node), CompoundNode.getName(node));
				}
				else if(node.isType(NodeType.GENE)) {
					Integer orgId = GeneNode.getOrganismGeneid(node);
					mappedGenes.put(orgId, GeneNode.getSymbol(node));
					geneIdForDataId.put(orgId, GeneNode.getGeneid(node).toString());
				}
			}
			
			if( (mappedCompounds.isEmpty() && !inputCompounds.isEmpty()) ||
					(mappedGenes.isEmpty() && !inputGenes.isEmpty()) ) {
					throw new Exception("We were unable to map any of the genes and/or compounds " +
							"that you provided. Please make sure that the correct organism is selected " +
							"and the data is in the correct format, and try again.");
			}
				
			
			for(String id: inputCompounds) {
				if(!mappedCompounds.containsKey(id))
					missingCompounds.add(id);
			}
			for(Integer id: inputGenes) {
				if(!mappedGenes.containsKey(id))
					missingGenes.add(id);
			}
			
			taskMonitor.setStatus("Getting mappings for concepts...");
			if(!conceptData.isEmpty()) {
				MetabolicNetworkService networkService = new MetabolicNetworkService(
						HttpRequestType.POST);
				Response<MetabolicNetwork> networkResponse = networkService
				.retrieveNetworkOfTypeForCidsAndGeneids(
						NetworkType.CREG, null, inputGenes,
						organism.getTaxid());
				
				if (networkResponse == null || !networkResponse.getResponseStatus().isSuccess() || interrupted)
					throw new Exception("Failed to retrieve concepts.");
				
				MetabolicNetwork net = networkResponse.getResponseValue();
				MetScapeNetworks.addToRootGraph(net);

				for(Concept concept: conceptData.getAllConcepts()) {
					boolean hasMapping = false;
					for(Integer dataId: concept.getGeneIds()) {
						String geneId = geneIdForDataId.get(dataId);
						if ((geneId != null) && (Cytoscape.getCyNode(geneId) != null)) {
							hasMapping = true;
							addBranchToConceptMap(geneId, concept);
						}
					}

					if(!hasMapping) 
						missingConcepts.add(concept);
				}
			}
			
			PluginData session = MetScapePlugin.getPluginData();
			session.setCurrentCompoundData(compoundData);
			session.setCurrentGeneData(geneData);
			session.setCurrentConceptData(conceptData);
			session.setCurrentOrganism(organism);
			session.setDefaultCompounds(mappedCompounds);
			session.setDefaultGenes(mappedGenes);
			
			if(!missingCompounds.isEmpty() ||
					!missingGenes.isEmpty() ||
					!missingConcepts.isEmpty()) {
				MissingDataDialog missingDataDialog = new MissingDataDialog
					(Cytoscape.getDesktop(),missingCompounds,missingGenes,missingConcepts);
				missingDataDialog.setVisible(true);
			}
			
			
			taskMonitor.setStatus("Complete");
			taskMonitor.setPercentCompleted(100);
		}

		catch (Throwable t) {
			if (!interrupted)
				taskMonitor.setException(t,
						"An error occurred while loading data for the MetScape session: "
								+ t.getLocalizedMessage());
		}
	}
	
	private void addBranchToConceptMap(String nodeId, Concept concept) {
		List<Concept> concepts = conceptData.getConceptMap().get(nodeId);
		if (concepts == null) {
			concepts = new ArrayList<Concept>();
		}
		concepts.add(concept);
		conceptData.getConceptMap().put(nodeId, concepts);
		String nodeType = Attributes.node.getStringAttribute(nodeId,"Type");
		String neighborType;
		if(nodeType.equals("Gene")) {
			neighborType = "Enzyme";
		}	
		else if(nodeType.equals("Enzyme")) {
			neighborType = "Reaction";
		}
		else if(nodeType.equals("Reaction")) {
			neighborType = "Compound";
		}
		else return;
			
		int[] edgeIndices = Cytoscape.getRootGraph().
			getAdjacentEdgeIndicesArray(Cytoscape.getCyNode(nodeId).getRootGraphIndex(), true,true,true);
		for(int edgeIndex: edgeIndices){ // need to make source/target compliant
			int sourceIndex = Cytoscape.getRootGraph().getEdgeSourceIndex(edgeIndex);
			int targetIndex = Cytoscape.getRootGraph().getEdgeTargetIndex(edgeIndex);
			CyNode source = (CyNode) Cytoscape.getRootGraph().getNode(sourceIndex);
			CyNode target = (CyNode) Cytoscape.getRootGraph().getNode(targetIndex);
			String neighborId;
			if(!target.getIdentifier().equals(nodeId))
				neighborId = target.getIdentifier();
			else neighborId = source.getIdentifier();
			if(Attributes.node.getAttribute(neighborId,"Type").equals(neighborType)
					&& !concepts.contains(neighborId)) {
				addBranchToConceptMap(neighborId,concept);
			}
		}
	}
}
